using System;
using System.Linq;
using System.Collections.Generic;
using Jbe.Composite.Extensions;
using System.Collections.ObjectModel;
using Jbe.TestSuite.Infrastructure.Interface.Documents;
using System.Reflection;
using System.IO;
using Jbe.TestSuite.Infrastructure.Interface;
using Microsoft.Practices.Composite.Regions;
using System.Collections.Specialized;
using Microsoft.Practices.Composite.Presentation.Commands;

namespace Jbe.TestSuite.Infrastructure.Module.Applications
{
    // TODO: Remove dependency to regions
    public class DocumentManager : Model, IDocumentManager
    {
        private static readonly PropertyInfo ActiveDocumentProperty = TypeManager.GetProperty<DocumentManager>(x => x.ActiveDocument);


        private readonly DelegateCommand<DocumentType> newCommand;
        private readonly DelegateCommand openCommand;
        private readonly DelegateCommand saveCommand;
        private readonly DelegateCommand saveAsCommand;
        private readonly DelegateCommand closeCommand;
        private readonly IRegionManager regionManager;
        private readonly IRegion contentRegion;
        private readonly IFileDialogService fileDialogService;
        private readonly IMessageService messageService;
        private readonly ObservableCollection<Document> documents;
        private readonly ReadOnlyObservableCollection<Document> readOnlyDocuments;
        private readonly HashSet<DocumentType> documentTypes;
        private readonly List<IActiveDocumentProvider> activeDocumentProviders;
        private Document activeDocument;


        public DocumentManager(IRegionManager regionManager, IFileDialogService fileDialogService, IMessageService messageService)
        {
            this.regionManager = regionManager;
            this.contentRegion = regionManager.Regions[ShellRegionNames.ContentRegion];
            this.fileDialogService = fileDialogService;
            this.messageService = messageService;
            this.documents = new ObservableCollection<Document>();
            this.readOnlyDocuments = new ReadOnlyObservableCollection<Document>(documents);
            this.documentTypes = new HashSet<DocumentType>();
            this.activeDocumentProviders = new List<IActiveDocumentProvider>();
            this.newCommand = new DelegateCommand<DocumentType>(New, CanNew);
            this.openCommand = new DelegateCommand(Open, CanOpen);
            this.saveCommand = new DelegateCommand(Save, CanSave);
            this.saveAsCommand = new DelegateCommand(SaveAs, CanSave);
            this.closeCommand = new DelegateCommand(Close, CanClose);
            DocumentCommands.New.RegisterCommand(newCommand);
            DocumentCommands.Open.RegisterCommand(openCommand);
            DocumentCommands.Save.RegisterCommand(saveCommand);
            DocumentCommands.SaveAs.RegisterCommand(saveAsCommand);
            DocumentCommands.Close.RegisterCommand(closeCommand);

            contentRegion.ActiveViews.CollectionChanged += ActiveViewsCollectionChanged;
        }


        public ReadOnlyObservableCollection<Document> Documents { get { return readOnlyDocuments; } }

        public Document ActiveDocument
        {
            get { return activeDocument; }
            private set
            {
                if (activeDocument != value)
                {
                    activeDocument = value;
                    RaisePropertyChanged(ActiveDocumentProperty);
                    UpdateCommands();
                }
            }
        }


        public void Register(DocumentType documentType)
        {
            documentTypes.Add(documentType);

            if (documentType.CanNew())
            {
                regionManager.Regions[ShellRegionNames.NewMenuItems].Add(documentType);
                regionManager.Regions[ShellRegionNames.NewButtonItems].Add(documentType);
            }

            UpdateCommands();
        }

        public void Register(IActiveDocumentProvider activeDocumentProvider)
        {
            activeDocumentProviders.Add(activeDocumentProvider);
        }

        private bool CanNew(DocumentType documentType) { return documentType.CanNew(); }

        private void New(DocumentType documentType)
        {
            Document document = documentType.New();
            documents.Add(document);
            ActiveDocument = document;
        }

        private bool CanOpen() { return documentTypes.Any(d => d.CanOpen()); }

        public void Open()
        {
            IEnumerable<DocumentType> openTypes = documentTypes.Where(d => d.CanOpen());
            string fileName;

            if (fileDialogService.ShowOpenFileDialog(openTypes, out fileName))
            {
                OpenFile(fileName);
            }
        }

        public void OpenFile(string fileName)
        {
            IEnumerable<DocumentType> openTypes = documentTypes.Where(d => d.CanOpen());
            DocumentType documentType = openTypes.FirstOrDefault(d => d.FileExtension == Path.GetExtension(fileName).TrimStart('.'));
            if (documentType == null)
            {
                messageService.ShowError("This document is not supported.", fileName);
            }
            else
            {
                Document document = documentType.Open(fileName);
                documents.Add(document);
                ActiveDocument = document;
            }
        }

        private bool CanSave() { return documentTypes.Any(d => d.CanSave(ActiveDocument)); }

        private void Save() { Save(ActiveDocument); }

        private void Save(Document document)
        {
            if (File.Exists(document.FileName))
            {
                IEnumerable<DocumentType> saveTypes = documentTypes.Where(d => d.CanSave(document));
                DocumentType documentType = saveTypes.First(d => d.FileExtension == Path.GetExtension(document.FileName).TrimStart('.'));
                documentType.Save(document, document.FileName);
            }
            else
            {
                SaveAs(document);
            }
        }

        private void SaveAs() { SaveAs(ActiveDocument); }

        private void SaveAs(Document document)
        {
            IEnumerable<DocumentType> saveTypes = documentTypes.Where(d => d.CanSave(document));
            string fileName;

            if (fileDialogService.ShowSaveFileDialog(saveTypes, document, out fileName))
            {
                DocumentType documentType = saveTypes.First(d => d.FileExtension == Path.GetExtension(fileName).TrimStart('.'));
                documentType.Save(document, fileName);
            }
        }

        private bool CanClose() { return ActiveDocument != null; }

        private void Close() { Close(ActiveDocument); }

        private void Close(Document document)
        {
            if (ActiveDocument == document)
            {
                ActiveDocument = null;
            }
            documents.Remove(document);
        }

        private void ActiveViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Document activeDocument = null;
            
            foreach (IActiveDocumentProvider activeDocumentProvider in activeDocumentProviders)
            {
                activeDocument = activeDocumentProvider.GetActiveDocument();
                if (activeDocument != null) { break; }
            }

            ActiveDocument = activeDocument;
        }

        private void UpdateCommands()
        {
            openCommand.RaiseCanExecuteChanged();
            saveCommand.RaiseCanExecuteChanged();
            saveAsCommand.RaiseCanExecuteChanged();
            closeCommand.IsActive = closeCommand.CanExecute(null);
            closeCommand.RaiseCanExecuteChanged();
        }
    }
}